
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	DrawingView.m
 *
 *	This view represents the page that the image is drawn onto. It is
 *	a subview of the DocView. The DocView is the document view of
 *	the ClipView. This view's real size grows with the scale but the
 *	bounds always stays the same.
 *
 *	An offscreen buffer, bufferId, is used to draw into and then
 *	this buffer is composited onscreen. This technique allows for the
 *	separation of the static drawing from the temporal drawing (modal
 *	loop redrawing and control point display).  The static drawing takes
 *	place in the buffer while the temporal drawing takes place in this view.
 *	The static drawing is stuff that is complex and that will stay around for
 *	a while. Saving this drawing in a buffer elimates having to redraw it
 *	for something simple like drawing a control point or something.
 *	(The bufferId is the content view of a plain window the size of the
 *	content view of this view's window.)
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-17-91		Added this comment and fixed the preview section.
 */

#import "DrawingView.h"
#import "DocView.h"
#import "Document.h"
#import "GraphicImport.h"
#import "GraphicList.h"
#import "ImportPanel.h"
#import "SaveAsPanel.h"
#import "epsf.h"

#import "DrawingViewWraps.h"

#import <appkit/Cell.h>
#import <appkit/ClipView.h>
#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/Text.h>
#import <appkit/Pasteboard.h>
#import <appkit/PrintInfo.h>
#import <appkit/nextstd.h>

#import <dpsclient/dpsclient.h>
#import <dpsclient/wraps.h>
#import <math.h>

extern char	ControlFont[ ];

extern const NXRect  DefaultContentRect;

static char	EpsfPboard[] = "Epsf Pasteboard Type";

static char	EpsfProcSet[] = "EPSF_Illustrator_abbrev 0 0";

/*
*	Timers used to automatically scroll when the mouse is
*	outside the drawing view and not moving.
*/
static void startTimer(NXTrackingTimer ** timer, int *timermask, id window)
{
	if (!*timer)
	{
		*timer = NXBeginTimer(NULL, 0.15, 0.2);
		*timermask = NX_TIMERMASK;
		[window addToEventMask:NX_TIMERMASK];
	}
}

static void stopTimer(NXTrackingTimer **timer, int *timermask, id window)
{
	if (*timer)
	{
		NXEndTimer(*timer);
		*timer = NULL;
		*timermask = 0;
		[window removeFromEventMask:NX_TIMERMASK];			
	}
}

void compositeBuffer(int gstate, const NXRect *srce, const NXPoint *dest, int op)
{
  	PScomposite(NX_X(srce), NX_Y(srce), NX_WIDTH(srce), NX_HEIGHT(srce),
                gstate, dest->x, dest->y, op);
}

/*
*	The next two procedures are for the drawing buffer.
*	This buffer is used to prevent unnecessary drawing by
*	retaining images within offscreen windows.
*
*	Create a plain window the size of the rectangle passed in and
*	then insert a view into the window as a subview. A clip view
*	is swapped for the content view so the buffer can be scrolled
*	when this view is scrolled.
*/
static id createBuffer(const NXSize *winSize)
{
	id		buffer, clipview, window;

	NXRect	contRect;
	
	contRect.origin.x = contRect.origin.y = 0;
	contRect.size = *winSize;
	window = [Window newContent:&contRect
				style:NX_PLAINSTYLE
				backing:NX_RETAINED
				buttonMask:0
				defer:NO] ;

	buffer = [[[[View  newFrame:&contRect] allocateGState] setFlipped:NO] setClipping:NO];
	clipview = [[[ClipView  new]  setFlipped:NO]  setDisplayOnScroll:NO];
	[[window  setContentView:clipview ] free];
	[clipview  setDocView:buffer];
	[clipview  allocateGState];

	[window  display];

	return buffer;
}

/*
*	Resize the buffer and its window. Called when this view's
*	window resizes. (The buffer is enlarged by a bit as an 
*	added insurance.)
*/
void resizeBuffer(id buffer, const NXSize*newSize)
{
	NXRect		frameRect, contRect;
	
	[buffer  getFrame:&frameRect];
	if (newSize->width > frameRect.size.width || newSize->height > frameRect.size.height)
	{
		contRect.origin.x = contRect.origin.y = 0.0;
		contRect.size = *newSize;
		NXInsetRect(&contRect, -10.0, -10.0);
		[Window  getFrameRect:&frameRect
					forContentRect:&contRect
						style:NX_PLAINSTYLE];
		[[buffer window]  sizeWindow:frameRect.size.width :frameRect.size.height];
		[buffer sizeTo:newSize->width :newSize->height];
	}
}

@implementation DrawingView

+newFrame:(const NXRect *) frameRect
{	
	self = [super newFrame:frameRect]; 
	[[self allocateGState] setClipping:NO]; 
	
	graphiclistId = [GraphicList  new];
	selectedlistId = [GraphicList  new];

	hitPoint = [NXApp  hitPoint];
	upathBuffer = [NXApp upathBuffer];

	bufferId = createBuffer(&DefaultContentRect.size);

	return self;
}

/* Free any unplaced imported object. */
- free
{
	[graphicId  free];
	[[graphiclistId  freeObjects]  free];
	[selectedlistId  free];

	[[bufferId  window]  free];
	
	return [super  free];
}

/*
*	Aids for understanding the operation of the application.
*	Moves the offscreen buffer onscreen.
*/
- showBuffer:sender
{
	[[bufferId window] moveTo:10 :10];
	[[bufferId window]  orderFront:self];

	[window  orderFront:self];

	return self;
}

- hideBuffer:sender
{
	[[bufferId window]  orderOut:self];

	return self;
}

/*
*	Returns the size of the control point scaled to reflect the current scale.
*/
- (float) controlPointSize
{
	return  FONTSIZE * (1.0/[superview  scale]);
}

/*
*	Scale the hit setting. Using an unscaled hit setting would be like
*	using a boxing glove on a 400% scale.
*/
- (float) hitSetting
{	
	return ([NXApp hitSetting] * (1.0/[superview  scale]));
}

- buffer
{
	return bufferId;
}

- image
{
	return imageId;
}

- setDirty:(BOOL) flag
{
	dirty = flag;
	[window  setDocEdited:flag];

	return self;
}

- (BOOL)isDirty
{
	return dirty;
}

- (BOOL)isEmpty
{
    return [graphiclistId count] == 0;
}

- (BOOL)isSelected
{
    return [selectedlistId count] > 0;
}

/*
*	Set the cursor to be the intersection of the bounds
*	and the visible portion of this view.
*/
- resetCursorRects
{
	NXRect		visRect;

	[self getVisibleRect:&visRect];
	NXIntersectionRect(&bounds, &visRect);
	[self addCursorRect:&visRect  cursor:[NXApp  cursor]];

	return self;
}

/*
*	When the drawing view moves, then move bufferId so that
*	the composites from bufferId are taken from the correct spot.
*/
- moveTo:(NXCoord)x :(NXCoord)y
{
	[super moveTo:x :y];
	[bufferId moveTo:x :y];

	return self;
}

/*
*	A scale as well needs to be reflected in the offscreen buffer.
*/
- scale:(NXCoord)x :(NXCoord)y
{
	[super scale:x :y];
	[bufferId scale:x :y];

	return self;
}

/*
*	This method copies the PostScript code for the graphics and writes it to the
*	stream passed in. Includes the preview image when appropriate.
*/
- writePSToStream:(NXStream *) stream
{
	id			nximageId, templist;

	NXRect		bbox;

	if (stream && [selectedlistId  count])
	{
		nximageId = NULL;
		imageId = NULL;
		templist = graphiclistId;
		graphiclistId = selectedlistId;
		[graphiclistId  getBounds:&bbox];
		if ([[SaveAsPanel  new]  format] == SAVE_EPSPREVIEW)
		{
			nximageId = [[NXImage  alloc]  initSize:&bbox.size];
			[nximageId  useCacheWithDepth:NX_TwoBitGrayDepth];
			if ([nximageId  lockFocus])
			{
				PStranslate(-bbox.origin.x, -bbox.origin.y);
				PSsetgray(NX_WHITE);
				NXRectFill(&bbox);
				[graphiclistId  drawObject:&bbox  withFlags:NOFLAGS  inView:self];

				imageId = [[NXBitmapImageRep  alloc]  initData:NULL  fromRect:&bbox];
				[nximageId  unlockFocus];
			}
		}

		[self copyPSCodeInside:&bbox to:stream];

		selectedlistId = graphiclistId;
		graphiclistId = templist;	
		[nximageId  free];
		[imageId  free];
		imageId = NULL;
	}

	return self;
}

/*   Pasteboard-related target/action methods */
/*  Calls copy:, then removes and frees the objects in the selection.  */
- cut:sender
{
	float			knobsize;

	NXRect		rect;

	if ([selectedlistId  count])
	{
		knobsize = -[self  controlPointSize]/2;
		[selectedlistId  getBounds:&rect];
		NXInsetRect(&rect, knobsize, knobsize);

		if (sender != self)
			[self  copy:sender];
		[graphiclistId  removeObjectsIn:selectedlistId];
		[selectedlistId  freeObjects];

		[self  display:&rect  :1];
		[self  setDirty:YES];

		return self;
	}

	return nil;
}

/*  Calls cut bypassing the copy.  */
- delete:sender
{
	return [self  cut:self];
}

/*
 *	Puts all the objects in the selected list into the Pasteboard by
 *	archiving the list. See the draw program for placing the objects
 *	in the Pasteboard as a PostScript image. Much of the work is
 *	available already. The right hooks just have to be added.
 */
- copy:sender
{
	BOOL		error = YES;

	id			pasteboardId = [Pasteboard  new];

	char			*dataptr;

	const char	*types[1];

	int			length, maxlen;

	NXStream	*stream;   

	NXTypedStream *ts;

	if ([selectedlistId  count])
	{
		types[0] = EpsfPboard;

		stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
		if (stream)
		{
			ts = NXOpenTypedStream(stream, NX_WRITEONLY);
			if (ts)
			{
				NXWriteRootObject(ts, selectedlistId);
				NXCloseTypedStream(ts);
				NXGetMemoryBuffer(stream, &dataptr, &length, &maxlen);

				[pasteboardId  declareTypes:types  num:2  owner:[self class]];
				[pasteboardId  writeType:EpsfPboard  data:dataptr  length:length];
				error = NO;
			}
			NXCloseMemory(stream, NX_FREEBUFFER);
		}
	}

	return (error ? nil : self);
}

/*	Routines to check the types in the Pasteboard */
static BOOL matchPasteType(char *const *types, const char *type)
{
	if (types)
		while (*types)
			if (!strcmp(*types++, type))
				return YES;

	return NO;
}

/*
 *	Validates the pasteboard types and returns the preferred one.
 */
static const char *drawPasteType(char *const *types)
{
	if (matchPasteType(types, EpsfPboard))
		return EpsfPboard;
	if (matchPasteType(types, NXPostScriptPboard))
		return NXPostScriptPboard;
	if (matchPasteType(types, NXTIFFPboardType))
		return NXTIFFPboardType;

	return NULL;
}

 /*
 *	Pastes any type available from the Pasteboard into the DrawingView.
 *	If the type in the Pasteboard is the internal type, then the objects
 *	are simply added to the selected list and graphic list.  If it is of PostScript
 *	or Tiff type then create a GraphicImport object using the contents of the
 *	Pasteboard.
 */
- paste:sender
{
	id			objectId,
				pastelistId,
				pasteboardId = [Pasteboard  new];

	char			*dataptr;

	const char	*type;

	int			length;

	float			knobsize;

	NXPoint		offset;

	NXRect		drawRect, visRect;

	NXStream	*stream;

	NXTypedStream *ts;

	type = drawPasteType([pasteboardId  types]);
	if (type)
	{
		[self  lockFocus];
			[self  deselectObject:selectedlistId];
		[self  unlockFocus];
		[pasteboardId  readType:type data:&dataptr length:&length];
		stream = NXOpenMemory(dataptr, length, NX_READONLY);
		if (strcmp(type, EpsfPboard) == 0)
		{
			ts = NXOpenTypedStream(stream, NX_READONLY);
			pastelistId = NXReadObject(ts);
			if ([pastelistId  count])
			{
				[graphiclistId  insertObjectsIn:pastelistId];
				[selectedlistId  insertObjectsIn:pastelistId];
				[selectedlistId  setSelected:YES];
			}
			NXCloseTypedStream(ts);
		}
		else if (strcmp(type, NXPostScriptPboard) == 0 ||
				strcmp(type, NXTIFFPboardType) == 0)		{
			objectId = [[GraphicImport alloc]  initFromStream:stream];
			[graphiclistId  addObject:objectId];
			[selectedlistId  addObject:objectId];
			[selectedlistId  setSelected:YES];
		}
		NXCloseMemory(stream, NX_SAVEBUFFER);
		
		if ([selectedlistId  count])
		{
			knobsize = -[self  controlPointSize]/2;
			[selectedlistId  getBounds:&drawRect];
			NXInsetRect(&drawRect, knobsize, knobsize);

			[self  getVisibleRect:&visRect];
			offset.x = (visRect.origin.x + visRect.size.width/2.0) -
						(drawRect.origin.x + drawRect.size.width/2.0);
			offset.y = (visRect.origin.y + visRect.size.height/2.0) -
						(drawRect.origin.y + drawRect.size.height/2.0);

			[selectedlistId  moveAll:&offset];
			drawRect.origin.x += offset.x;
			drawRect.origin.y += offset.y;
			[self  display:&drawRect  :1];
			[self  setDirty:YES];
		}
	}
	else
		return nil;

	return self;
}

/*
 *	Selects all the items in the graphiclistId.
 */
- selectAll:sender
{
	int		i;

	float		knobsize;

	NXRect	rect;

	i = [graphiclistId  count];
	if (i  && i > [selectedlistId  count])
	{
		[selectedlistId  free];
		selectedlistId = [graphiclistId  copy];
		[selectedlistId  setSelected:YES];

		knobsize = -[self  controlPointSize]/2;
		[selectedlistId  getBounds:&rect];
		NXInsetRect(&rect, knobsize, knobsize);

		[self  lockFocus];
			[self  drawControl:selectedlistId  forRect:&rect  withFlags:NOFLAGS];
		[self  unlockFocus];
		[window  flushWindow];
   	}
 
	return self;
}

/*
 *	Brings each of the items in the selected list to the front of the
 *	graphics list. The first item selected will be at the front of the
 *	graphics list with the others following immediately behind.
 */
- bringToFront:sender
{
	id		object;

	BOOL	change = NO;

	int		i, j, index;

	float		knobsize;

	NXRect	rect;

	j = [selectedlistId  count];
	for (i = 0; i < j; i++)
	{
		object = [selectedlistId  objectAt:i];
		index = [graphiclistId  indexOf:object];
		if (index != i)
		{
			[graphiclistId  removeObjectAt:index];
			[graphiclistId  insertObject:object at:i];
			change = YES;
		}
	}

	if (change)
	{
		knobsize = -[self  controlPointSize]/2;
		[selectedlistId  getBounds:&rect];
		NXInsetRect(&rect, knobsize, knobsize);

		[self  display:&rect  :1];
		[self  setDirty:YES];
	}

	return self;
}

/*
 *	Moves each of the items in the selected list to the back of the
 *	graphics list. The last item selected will be at the back of the
 *	graphics list with the others following immediately ahead.
 */
- sendToBack:sender
{
	id		object;

	BOOL	change = NO;

	int		numS, numG, i, index;

	float		knobsize;

	NXRect	rect;

	numS = [selectedlistId  count] - 1;
	numG = [graphiclistId  count] - 1;
	for (i = numS; i >= 0; i--)
	{
		object = [selectedlistId  objectAt:i];
		index = [graphiclistId  indexOf:object];
		if (numG - index != numS - i)
		{
			[graphiclistId  removeObjectAt:index];
			[graphiclistId  insertObject:object at:(numG - (numS - i))];
			change = YES;
		}
	}

	if (change)
	{
		knobsize = -[self  controlPointSize]/2;
		[selectedlistId  getBounds:&rect];
		NXInsetRect(&rect, knobsize, knobsize);

		[self  display:&rect  :1];
		[self  setDirty:YES];
	}

	return self;
}

- makeOriginalUsing:(SEL) aMethod
{
	float			knobsize;

	NXRect		rect_start, rect_end;

	knobsize = -[self  controlPointSize]/2;
	[selectedlistId  getBounds:&rect_start];
	NXInsetRect(&rect_start, knobsize, knobsize);

	if ([selectedlistId  makeObjectsPerform:aMethod])
	{
		[selectedlistId  getBounds:&rect_end];
		NXInsetRect(&rect_end, knobsize, knobsize);

		NXUnionRect(&rect_start, &rect_end);
		[self  lockFocus];
			[self  drawSelf:&rect_end  :1];
		[self  unlockFocus];
		[self  setDirty:YES];
		[window  flushWindow];	
	}

	return self;
}

/*
 *	Sizes the selected objects to have their original sizes.
 */
- originalSize:sender
{
	return [self  makeOriginalUsing:@selector(setOriginalSize)];
}

/*
 *	Sizes the selected objects to have the save width/height ratio as their
 *	original bounding boxes. Centers them about their centers.
 */
- originalRatio:sender
{
	return [self  makeOriginalUsing:@selector(setOriginalRatio)];
}

/*
 * Scrolls to rectangle passed in if it is not in visible portion of the view.
 * If the rectangle is larger in width or height than the view, the scrollRectToVisible
 * method is not altogether consistent. As a result, the rectangle contains only
 * the image that was previously visible.
 */
 - scrollToRect:(const NXRect *)toRect
{
	NXRect		visRect;

	[self getVisibleRect:&visRect];
	if (!NXContainsRect(&visRect, toRect))
	{
		[window disableFlushWindow];
		[self scrollRectToVisible:toRect];
		[window reenableFlushWindow];

		startTimer(&timer, &timermask, window);
	}
	else
		stopTimer(&timer, &timermask, window);

	return self;
}

/*
*	Constrain the point within the view. An offset is needed because when
*	an object is moved, it is often grabbed in the center of the object. If the
*	lower left offset and the upper right offset were not included then part of
*	the object could be moved off of the view. (In some applications, that might
*	be allowed but in this one the object is constrained to always lie in the
*	page.)
*/
- constrainPoint:(NXPoint *)aPt  withOffset:(const NXSize*)llOffset  :(const NXSize*)urOffset
{
	float			margin;

	NXPoint		viewMin, viewMax;

	margin = ceil(FONTSIZE/2);

	viewMin.x = bounds.origin.x + llOffset->width + margin;
	viewMin.y = bounds.origin.y + llOffset->height + margin;

	viewMax.x = bounds.origin.x + bounds.size.width - urOffset->width - margin;
	viewMax.y = bounds.origin.y + bounds.size.height  - urOffset->height - margin;

	aPt->x = MAX(viewMin.x, aPt->x);
	aPt->y = MAX(viewMin.y, aPt->y);

	aPt->x = MIN(viewMax.x, aPt->x);	
	aPt->y = MIN(viewMax.y, aPt->y);

	return self;
}

/*
*	Constrain a rectangle within the view.
*/
- constrainRect:(NXRect *)aRect
{
	float			margin;
	
	NXPoint		viewMin, viewMax;

	margin = ceil(FONTSIZE/2);

	viewMin.x = bounds.origin.x + margin;
	viewMin.y = bounds.origin.y + margin;

	viewMax.x = bounds.origin.x + bounds.size.width  - aRect->size.width - margin;
	viewMax.y = bounds.origin.y + bounds.size.height - aRect->size.height - margin;

	aRect->origin.x = MAX(viewMin.x, aRect->origin.x);
	aRect->origin.y = MAX(viewMin.y, aRect->origin.y);

	aRect->origin.x = MIN(viewMax.x, aRect->origin.x );	
	aRect->origin.y = MIN(viewMax.y, aRect->origin.y);

	return self;
}

/*
 *	Redraws the graphic. The image from the buffer is composited
 *	into the window and then the changed object is drawn atop the
 *	old image. A copy of the image is necessary because when the
 *	window is scrolled the buffer is also scrolled. When the
 *	buffer is scrolled, the old image might have to be redrawn.
 *	As a result, a copy is created and the changes performed on the
 *	copy.  Care is taken to limit the amount of area that must be
 *	composited and redrawn. A timer is started is the scrolling rect
 *	moves outside the visible portion of the view.
 */
- redrawObject:objectId  :(int) pt_num
{
	id			copyId;

	BOOL		tracking,
				dirtyFlag = NO;

	int			old_mask;
	
	float			knobsize;

	NXPoint		pt, pt_last, pt_old, delta;
	
	NXRect		rect_now, rect_start, rect_last, rect_scroll, rect_vis;

	NXEvent		*event;

	timermask = 0;

	/*
	*  Create a copy of the selected object. If we scroll we will need to redraw
	*  the old object. If we do not create a copy we will not have an old object.
	*  The copy method copies the stream pointer and not the data
	*  if the imported file happens to be copied into the document.
	*/
	copyId = [objectId  copyTemp];

	knobsize = -[self  controlPointSize]/2;
	[copyId  getBounds:&rect_start];
	NXInsetRect(&rect_start, knobsize, knobsize);

	rect_now = rect_last = rect_start;

	/*
	*  The rect_scroll will cause scrolling whenever it goes outside the
	*  visible portion of the view.
	*/
	[copyId  getScrollRect:&rect_scroll  forPtNum:pt_num];
	NXInsetRect(&rect_scroll, knobsize, knobsize);

	[self  getVisibleRect:&rect_vis];
	NXIntersectionRect(&rect_vis, &rect_scroll);

	[copyId  getPoint:pt_num :&pt_last];
	pt_old = pt_last;

	old_mask = [window addToEventMask:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	if (event->type != NX_MOUSEUP)
	{
		tracking = YES;
		while (tracking)
		{
			/*
			*  If its a timer event than use the last point. It will be converted to
			*  the view's coordinate so it will appear as a new point.
			*/
			if (event->type == NX_TIMER)
				pt = pt_old;
			else
				pt = pt_old = event->location;

			[self convertPoint:&pt  fromView:nil];
			[copyId constrainPoint:&pt  forPtNum:&pt_num
					inRect:&bounds  withFlags:event->flags];

			delta.x = pt.x - pt_last.x;
			delta.y = pt.y - pt_last.y;

			if (delta.x || delta.y)
			{
				dirtyFlag = YES;
				/* Change the point location and get the new bounds. */
				[copyId setPoint:pt_num :&delta];
				[copyId  getBounds:&rect_now];
				NXInsetRect(&rect_now, knobsize, knobsize);

				/* Change the scrolling rectangle. */
				NXOffsetRect(&rect_scroll, delta.x, delta.y);
				[self  scrollToRect:&rect_scroll];

				/* Composite the old image and then redraw the new one. */
				compositeBuffer([bufferId gState], &rect_last, &rect_last.origin, NX_COPY);
				[self  drawObject:copyId  forRect:&rect_now  withFlags:REDRAWFLAG];
				[self  drawControl:copyId  forRect:&rect_now  withFlags:NOFLAGS];

				/* Sync up the drawing so it proceeds a little smoother. */
				[window flushWindow];
				NXPing();
				
				rect_last = rect_now;
				pt_last = pt;
			}
			else
				stopTimer(&timer, &timermask, window);

			event = [NXApp getNextEvent:NX_MOUSEUPMASK|
							NX_MOUSEDRAGGEDMASK|timermask];
			tracking = (event->type != NX_MOUSEUP);
		}
		stopTimer(&timer, &timermask, window);
	}
	[window setEventMask:old_mask];

	if (![graphiclistId  replaceObject:objectId  with:copyId])
		[graphiclistId  insertObject:copyId at:0];

	if (![selectedlistId  replaceObject:objectId  with:copyId])
		[selectedlistId  insertObject:copyId at:0];

	[objectId  freeTemp];

	if (dirtyFlag)
	{
		/*
		*  The view has already been focused and we know what
		*  has to be redrawn so call drawSelf:: instead of display
		*/
		NXUnionRect(&rect_last, &rect_start);
		[self drawSelf:&rect_start :1];
		[window flushWindow];
		NXPing();
		[self  setDirty:YES];
	}

	return self;
}

/*
 *	Moves the selected objects by performing a translate before drawing
 *	the objects. This approach is used because the objects are drawn
 *	into windows and drawing simply means compositing the windows.
 *
 *	The offsets constrain the selected object to stay within the dimensions
 *	of the view. 
 */
- moveObject:(NXEvent *)event
{
	BOOL		tracking,
				moveFlag = NO,
				dirtyFlag = NO;
	
	int			old_mask;

	long			time;

	float			knobsize;

	NXSize		llOffset, urOffset;

	NXPoint		pt, pt_last, pt_old, delta, delta_scroll;

	NXRect		rect_now, rect_start, rect_last, rect_scroll, rect_vis;

	/*
	*  Get the scrolling rectangle. Compare it against a reduced version of 
	*  the visible rect. If it turns out to be the larger then use the reduced
	*  rectangle so that the user is not playing pong when trying to
	*  move the image.
	*/
	knobsize = -[self  controlPointSize]/2;
	[selectedlistId  getScrollRect:&rect_scroll  forPtNum:-1];
	NXInsetRect(&rect_scroll, knobsize, knobsize);

	[self  getVisibleRect:&rect_vis];
	NXInsetRect(&rect_vis, rect_vis.size.width * 0.20, rect_vis.size.height * 0.20);
	if (rect_scroll.size.width > rect_vis.size.width || rect_scroll.size.height > rect_vis.size.height) 
	{
		NXInsetRect(&rect_scroll, MAX(0, (rect_scroll.size.width - rect_vis.size.width)/2),
				MAX(0, (rect_scroll.size.height - rect_vis.size.height)/2));
	}

	[selectedlistId  getBounds:&rect_start];
	NXInsetRect(&rect_start, knobsize, knobsize);
	rect_now = rect_last = rect_start;

	delta_scroll.x = rect_scroll.origin.x - rect_now.origin.x;
	delta_scroll.y = rect_scroll.origin.y - rect_now.origin.y;

	timermask = 0;
	time = event->time;
	pt_last = pt_old = event->location;
	[self  convertPoint:&pt_last  fromView:nil];
	
	/* Calculate where the mouse point falls relative to the object. */
	llOffset.width = pt_last.x - rect_start.origin.x;
	llOffset.height = pt_last.y - rect_start.origin.y;	
	urOffset.width = rect_start.origin.x + rect_start.size.width - pt_last.x;
	urOffset.height = rect_start.origin.y + rect_start.size.height - pt_last.y;

	/* Return nil if the the mouse was not dragged. */
	old_mask = [window addToEventMask:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	if (event->type != NX_MOUSEUP)
	{
		tracking = YES;
		while (tracking)
		{			
		    /*
		    *	Only move the object if a certain amount of time has elapsed
		    *	between mouse down and mouse up. Prevents accidently
		    *	moving objects when selecting.
		    */
		    if (event->time - time > MOVE_INTERVAL)
		    {
		    	moveFlag = YES;
			if (event->type == NX_TIMER)
				pt = pt_old;
			else
				pt = pt_old = event->location;
				
			[self  convertPoint:&pt fromView:nil];
			[self  constrainPoint:&pt  withOffset:&llOffset :&urOffset];
			[self  constrainPoint:&pt_last  withOffset:&llOffset :&urOffset];
			delta.x = pt.x - pt_last.x;
			delta.y = pt.y - pt_last.y;

			if (delta.x || delta.y)
			{
				dirtyFlag = YES;
				NXOffsetRect(&rect_now, delta.x, delta.y);
				[self  constrainRect:&rect_now];

				rect_scroll.origin.x = rect_now.origin.x + delta_scroll.x;
				rect_scroll.origin.y = rect_now.origin.y + delta_scroll.y;
				[self  scrollToRect:&rect_scroll];

				/*
				*  Composite the old image into the window and then
				*  translate the user space and draw the objects.
				*/
				compositeBuffer([bufferId gState], &rect_last, &rect_last.origin, NX_COPY);
				PSgsave();
					PStranslate(rect_now.origin.x - rect_start.origin.x,
						rect_now.origin.y - rect_start.origin.y);
					[self  drawObject:selectedlistId  forRect:&rect_start  withFlags:MOVEFLAG];
					[self  drawControl:selectedlistId  forRect:&rect_start 
							withFlags:NOFLAGS];
				PSgrestore();

				[window flushWindow];
				NXPing();
				
				rect_last = rect_now;
				pt_last = pt;
			}
			else
				stopTimer(&timer, &timermask, window);
		    }
		    event = [NXApp getNextEvent:NX_MOUSEUPMASK|
						NX_MOUSEDRAGGEDMASK|timermask];

		    tracking = (event->type != NX_MOUSEUP);
		}
		stopTimer(&timer, &timermask, window);

		if (dirtyFlag)
		{
			delta.x = rect_now.origin.x - rect_start.origin.x;
			delta.y = rect_now.origin.y - rect_start.origin.y;		
			[selectedlistId  moveAll:&delta];
	
			/*
			*  The view has already been focused and we know what
			*  has to be redrawn so call drawSelf:: instead of display
			*/
			NXUnionRect(&rect_now, &rect_start);
			[self drawSelf:&rect_start :1];
			[window flushWindow];
			NXPing();
			[self  setDirty:YES];
		}
	}

	[window setEventMask:old_mask];

	/*
	*  Return nil if the elapsed time is less than the amount necessary to
	*  consider the action a move.
	*/
	if (moveFlag)
		return self;
	else
		return nil;
}

-eraseRotatePoint:(NXRect *) drawnRect
{
	float			scale;

	NXRect		rotateRect;

	scale = 1.0 / [superview  scale];
	rotateRect.origin.x = rotatePoint.x - 8.0 * scale;
	rotateRect.origin.y = rotatePoint.y - 8.0 * scale;
	rotateRect.size.width = rotateRect.size.height = 16.0 * scale;
	if (!NXContainsRect(drawnRect, &rotateRect))
	{
		compositeBuffer([bufferId gState], &rotateRect, &rotateRect.origin, NX_COPY);
		[self  drawControl:selectedlistId  forRect:&rotateRect  withFlags:NOFLAGS];
	}

	return self;
}

-drawRotatePoint
{
	float			scale;

	NXPoint		compPt;

	scale = 1.0 / [superview  scale];
	compPt.x = rotatePoint.x - 8.0 * scale;
	compPt.y = rotatePoint.y - 8.0 * scale;
	[[[NXApp  getCursor:OP_ROTATE1]  image] composite:NX_SOVER  toPoint:&compPt];

	return self;
}

/*
*	Mark the point about which to rotate. Employ gravity near any
*	control points.	
*/
- rotateObjectStart:(NXEvent *)event
{
	id			objectId;

	int			pt_num;

 	rotatePoint = event->location; 
	[self convertPoint:&rotatePoint  fromView:nil];
	[self  lockFocus];
		if (objectId = [self  checkControl:&rotatePoint :&pt_num])
			[objectId  getPoint:pt_num  :&rotatePoint];
		[self  drawRotatePoint];
	[self  unlockFocus];
	[window  flushWindow];	

	return self;
}

/*
 *	Rotates the selected graphics about the rotatePoint selected
 *	from the previous mouse down. The image from the buffer
 *	is composited into the window and then the rotated object is
 *	drawn atop the old image. 
 *
 *	Care is taken to limit the amount of area that must be
 *	composited and redrawn. A timer is started is the scrolling rect
 *	moves outside the visible portion of the view.
 */
- rotateObject:(NXEvent *)event
{
	id			slist;
 
	BOOL		tracking,
				dirtyFlag = NO;

	int			old_mask;

	float			knobsize, marginsize;
	
	float			radians_start, radians_last, radians_delta;

	NXPoint		pt, pt_old, d;
	
	NXRect		rect_now, rect_start, rect_last, rect_scroll;

	slist = [selectedlistId  copyTemp];

	timermask = 0;
	marginsize = (1.0/[superview  scale] * SCROLL_MARGIN)/2;
 	rect_scroll.size.width = rect_scroll.size.height = marginsize * 2;

	knobsize = -[self  controlPointSize]/2;
	[slist  getBounds:&rect_start];
	NXInsetRect(&rect_start, knobsize, knobsize);

	rect_now = rect_last = rect_start;

	pt_old = pt = event->location;
	[self convertPoint:&pt fromView:nil];

	d.x = pt.x - rotatePoint.x; 
	d.y = pt.y - rotatePoint.y;

	radians_start = 0;
	if (d.x || d.y)
		radians_start = atan2(d.y,d.x);
	radians_last = radians_start;

	[self  lockFocus];
	old_mask = [window addToEventMask:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	if (event->type != NX_MOUSEUP)
	{
		tracking = YES;
		while (tracking)
		{
			/*
			*  If its a timer event than use the last point. It will be converted to
			*  the view's coordinate so it will appear as a new point.
			*/
			if (event->type == NX_TIMER)
				pt = pt_old;
			else
				pt = pt_old = event->location;

			[self  convertPoint:&pt  fromView:nil];

			d.x = pt.x - rotatePoint.x; 
			d.y = pt.y - rotatePoint.y;
			if (d.x || d.y)
			{
				dirtyFlag = YES;
				radians_delta = atan2(d.y, d.x) - radians_last;
				[slist constrainAngle:&radians_delta  withFlags:event->flags];

				/* Rotate the object about the given point at the given angle. */
				[slist  rotateAboutPoint:&rotatePoint  withAngle:radians_delta];
				[slist  getBounds:&rect_now];
				NXInsetRect(&rect_now, knobsize, knobsize);

				/* Make the scrolling rectangle the mouse point with a little margin. */
				rect_scroll.origin.x = pt.x - marginsize;
				rect_scroll.origin.y = pt.y - marginsize;
				[self  scrollToRect:&rect_scroll];

				/* Composite the old image and then redraw the new one. */
				NXIntegralRect(&rect_last);
				compositeBuffer([bufferId gState], &rect_last, &rect_last.origin, NX_COPY);
				[self  drawObject:slist  forRect:&rect_now  withFlags:REDRAWFLAG];
				[self  drawControl:slist  forRect:&rect_now  withFlags:NOFLAGS];
				[self  drawRotatePoint];

				/* Sync up the drawing so it proceeds a little smoother. */
				[window flushWindow];
				NXPing();
				
				rect_last = rect_now;
				radians_last = radians_delta + radians_last;
			}
			else
				stopTimer(&timer, &timermask, window);

			event = [NXApp getNextEvent:NX_MOUSEUPMASK|
							NX_MOUSEDRAGGEDMASK|timermask];
			tracking = (event->type != NX_MOUSEUP);
		}
		stopTimer(&timer, &timermask, window);
	}
	[window setEventMask:old_mask];
 		
	[graphiclistId  replaceObjectsIn:selectedlistId  with:slist];

 	[selectedlistId  freeTemp];
 	[selectedlistId  free];
	selectedlistId = slist;

	if (dirtyFlag)
	{
		/*
		*  The view has already been focused and we know what
		*  has to be redrawn so call drawSelf:: instead of display
		*/
		NXUnionRect(&rect_last, &rect_start);
		[self  drawSelf:&rect_start :1];
		[self  eraseRotatePoint:&rect_start];
		[window flushWindow];
		NXPing();
		[self  setDirty:YES];
	}
	[self  unlockFocus];

	return self;
}

/* Check to see whether a control point has been hit. */
 - checkControl:(const NXPoint *) p :(int *) pt_num
 {
	float		hitsetting;

	NXRect	hitRect;

	hitsetting = [self  hitSetting];	
	NXSetRect(&hitRect, p->x - hitsetting/2, p->y - hitsetting/2, hitsetting, hitsetting);

	return [selectedlistId  hitControl:&hitRect :pt_num  forSize:[self  controlPointSize]];
 }

/* 
*	Check to see whether an object has been hit. Return the selected list if
*	any object in it has been hit. Otherwise just return the individual
*	graphic.
*/
- checkObject:(const NXPoint *) p  in:listId  from:(int) start  to:(int) end
 {
	id		objectId;

	BOOL	flag;

	int		i;

	float		hitsetting;

	hitsetting = [self  hitSetting];	

	/*  Bounding Box */
	hitPoint->pts[0] = floor(p->x - hitsetting/2);
	hitPoint->pts[1] = floor(p->y - hitsetting/2);
	hitPoint->pts[2] = ceil(p->x + hitsetting/2);
	hitPoint->pts[3] = ceil(p->y + hitsetting/2);
	
	/*  Moveto */
	hitPoint->pts[4] = p->x - hitsetting/2;
	hitPoint->pts[5] = p->y - hitsetting/2;

	/* Rlineto's */
	hitPoint->pts[7] = hitsetting;
	hitPoint->pts[8] = hitsetting;
	hitPoint->pts[11] = -hitsetting;

	flag = (listId == graphiclistId);
	for (i = start; i < end; i++)
		if (objectId = [[listId  objectAt:i]  hitObject:hitPoint  ifNotSelected:flag])
			return objectId;

	return nil;
 }
 
 /*
 *	Set the object as selected, add the object to the selected list and
 *	then draw the control points.
 */
 - selectObject:objectId
 {
	NXRect		drawRect;

	[objectId  setSelected:YES];
	[selectedlistId  addObject:objectId];

	[self  getVisibleRect:&drawRect];
	[self  drawControl:objectId  forRect:&drawRect  withFlags:NOFLAGS];
	[window  flushWindow];

	return self;
}

/*
*	Remove the object (or objects) from the selected list and redraw the
*	control points for the visible portion. The control points are drawn
*	in this view since they can be drawn pretty quickly (and so the don't
*	need to be cached in an offscreen buffer somewhere).
*/
- deselectObject:objectId
{
	NXRect		drawRect;

	[self  getVisibleRect:&drawRect];

	[objectId  setSelected:NO];
	if (objectId == selectedlistId)
		[selectedlistId  empty];
	else
		[selectedlistId  removeObject:objectId];

	[self  drawControl:selectedlistId  forRect:&drawRect  withFlags:CLEARFLAG];
	[window  flushWindow];
	
	return self;
}

/*
*	Test for a mouse down hit on either the object or the control points.
*	This algorithm looks further into the list in order to check for mouse
*	hits on objects that lie below the currently selected object.
*/ 
- testObject:(NXEvent *)event
{
	id			objectId, newobjectId;

	int			pt_num, list_index;
	
	NXPoint		p;
 
 	p = event->location; 
	[self convertPoint:&p fromView:nil];
	[self  lockFocus];
		if (objectId = [self checkControl:&p :&pt_num])
		{
			[self  redrawObject:objectId  :pt_num];
		}	
		else
		{
			if ([selectedlistId  count] == 0 ||
				(event->flags & NX_SHIFTMASK) == NX_SHIFTMASK)
			{
				if (objectId = [self checkObject:&p  in:selectedlistId
							from:0  to:[selectedlistId  count]])
					[self  deselectObject:objectId];
				else  if (objectId = [self checkObject:&p  in:graphiclistId
							from:0  to:[graphiclistId  count]])
					[self  selectObject:objectId];
			}
			else
			{
			 	objectId = [self checkObject:&p  in:selectedlistId
							from:0  to:[selectedlistId  count]];
				if (!objectId || ![self  moveObject:event])
				{
					/*	Look further into the list first.    */
					list_index = 0;
					if (objectId)
						list_index = [graphiclistId  indexOf:objectId];
					if ((newobjectId = [self  checkObject:&p  in:graphiclistId
							from:list_index  to:[graphiclistId  count]]) ||
					     (newobjectId = [self  checkObject:&p  in:graphiclistId
					     		from:0  to:list_index]))
					{
						[self  deselectObject:selectedlistId];
						[self  selectObject:newobjectId];
					}
					else if (!objectId)		
						[self  deselectObject:selectedlistId];
				}
			}
		}
	[self  unlockFocus];	

	return self;
}

/*
 *	Free any previously unplaced imported object first. Next,
 *	pass the file name to the factory Tiff or Epsf object to create a new
 *	instance. If successful then remove items from the selected list.
 *
 *	If a point has been passed in then place the imported file at its
 *	original size with its left corner corresponding to p. If not
 *	then set the NXApp operation to OP_PLACE. The next mouse
 *	down will begin the modal loop for drawing out the sizing rectangle.
 */
- importFile:(const char *) file  at:(NXPoint *) p
{
	BOOL		ok;

	char			*end;

	NXStream	*stream;

	ok = NO;
	[graphicId  free];
	graphicId = NULL;
	if (file)
	{
		end = strrchr(file, '.');
		if (end)
		{
			if ([[ImportPanel  new]  format]  ==  IMPORT_COPY)
			{
				stream = NXMapFile(file, NX_READONLY);
				if (stream)
				{
					if (strncmp(end, ".tiff", 5) == 0 ||
						strncmp(end, ".ps", 3) == 0 ||
							strncmp(end, ".eps", 4) == 0)
					{
	 					graphicId = [[GraphicImport alloc]  initFromStream:stream];
						[graphicId  setFilename:file];
					}
					NXCloseMemory(stream, NX_SAVEBUFFER);
				}
				else
					Notify("Import Error", "Unable to open file.");
			}
			else
			{
				if (strncmp(end, ".tiff", 5) == 0 ||
					strncmp(end, ".ps", 3) == 0 ||
						strncmp(end, ".eps", 4) == 0)
				{
	 				graphicId = [[GraphicImport alloc]  initFromFile:file];
				}
			}

			if (graphicId)
			{
				[self  lockFocus];
					[self  deselectObject:selectedlistId];
				[self  unlockFocus];

				if (p)
				{
					[self  placeObjectAt:p];
					graphicId = NULL;
				}
				else
					[NXApp  setOperation:OP_IMPORT];
		
				[window  flushWindow];
				NXPing();
				ok = YES;
			}
		}
		else
			Notify("Import Error", "Unable to import file. Unrecognized file type.");
	}

	return ok ? self : nil;
}

/*
 *	Place the graphicId with its upper left corner at p;
 */
- placeObjectAt:(const NXPoint *) p
{
	float			knobsize;

	NXPoint		pt;

	NXRect		rect_draw;

	if (graphicId)
	{
		pt = *p;
		[graphiclistId  insertObject:graphicId at:0];
		[selectedlistId  insertObject:graphicId at:0];

		[graphicId  getBounds:&rect_draw];
		pt.y = pt.y - rect_draw.size.height;
		[graphicId  setOrigin:&pt];

		knobsize = -[self  controlPointSize]/2;
		[graphicId  getBounds:&rect_draw];
		NXInsetRect(&rect_draw, knobsize, knobsize);

		[self  display:&rect_draw :1];
	}

	return self;
}

/*
 *	Begins the setup for placing an imported file into the document.
 *	The object for the file is created and then waits for the mouse
 *	down to begin placement and sizing. This method first gets the
 *	object and then messages the redrawObject method to draw
 *	the subsequent sizing rectangles.
 */
- importObject:(NXEvent *)event
{
	float			knobsize;

	NXPoint		p;

	NXRect		rect_draw;
 
 	if (graphicId)
	{
		p = event->location; 
		[self convertPoint:&p fromView:nil];
		if ([[ImportPanel  new]  dragToSize])
		{
			[NXApp  setOperation:OP_PLACE];

			NXSetRect(&rect_draw, p.x, p.y-SIZE_MIN, SIZE_MIN, SIZE_MIN);
			[graphicId  setBounds:&rect_draw];

			knobsize = -[self  controlPointSize]/2;
			NXInsetRect(&rect_draw, knobsize, knobsize);

			[self  lockFocus];
				[self  drawObject:graphicId  forRect:&rect_draw  withFlags:REDRAWFLAG];
				[self  drawControl:graphicId  forRect:&rect_draw  withFlags:NOFLAGS];
				[self  redrawObject:graphicId  :8];
			[self  unlockFocus];

			graphicId = [selectedlistId  objectAt:0];
		}
		else
			[self  placeObjectAt:&p];

		if ([graphicId  error])
		{
			[self  lockFocus];
				[self  deselectObject:graphicId];
			[self  unlockFocus];
			[graphiclistId  removeObject:graphicId];
			[graphicId  free];
		}
		else
			[self  setDirty:YES];
		graphicId = NULL;
	}

	return self;
}

 /*
 *	Depending on the current operation, check for selection, zoom or
 *	import a file.
 */
- mouseDown:(NXEvent *)event
{
	int		operation;

	operation = [NXApp  operation];
	switch (operation)
	{
		case  OP_SELECT:
			[self  testObject:event];
			break;
		case  OP_ZOOMUP:
		case  OP_ZOOMDOWN:
			[nextResponder  scaleDrawView:self  withEvent:event];
			break;
		case  OP_ROTATE1:
			[self  rotateObjectStart:event];
			[NXApp  setOperation:OP_ROTATE2];
			break;
		case  OP_ROTATE2:
			[self  rotateObject:event];
			[NXApp  setOperation:OP_ROTATE1];
			break;
		case  OP_IMPORT:
			[self  importObject:event];
			[NXApp  clearOperation];
			break;
	}

	return self;
}

/*
 *	Changes the cursor to the reduce cursor if the shift key is
 *	pressed when zooming.
 */
- flagsChanged:(NXEvent *) event
{
	BOOL	shift;

	int		operation;

	operation = [NXApp  operation];
	shift = (event->flags & NX_ALTERNATEMASK) == NX_ALTERNATEMASK;

	if (operation == OP_ZOOMUP && shift)
		[NXApp  setOperation:OP_ZOOMDOWN];
	else if (operation == OP_ZOOMDOWN && !shift)	
		[NXApp  setOperation:OP_ZOOMUP];
	else
		return [super  flagsChanged:event];

	return self;
}

/*
 *	Deletes the selected objects.
 */
- keyDown:(NXEvent *) event
{
	if ([NXApp  operation] == OP_SELECT && 
	     event->data.key.charSet == NX_ASCIISET &&
	     event->data.key.charCode == NX_DELETE)
		return [self  cut:self];
	else
		return nil;
}

/*
*	Draw the control points using the user path buffer to hold
*	the data for the xyshow. Having the object fill in the data 
*	structure allows for combining the control points for multiple
*	objects. This increases drawing performance by reducing
*	the number of operations. But since this operation is performed
*	after the objects have been drawn, the control points are above
*	the graphics. This approach may or may not be desired.
*/
- drawControl:object  forRect:(NXRect *)r  withFlags:(int)flags
{
	float			knobsize;

	NXPoint		lastpoint;
	
	NXRect		rect;

	if (r)
		rect = *r;
	else
		[self  getVisibleRect:&rect];

	if (flags & CLEARFLAG)
		compositeBuffer([bufferId gState], &rect, &rect.origin, NX_COPY);

	lastpoint.x = 0;
	lastpoint.y = 0;

	upathBuffer->num_ops = 0;
	upathBuffer->num_pts = 0;

	knobsize = [self  controlPointSize];
	NXInsetRect(&rect, -knobsize/2, -knobsize/2);
	[object  putControlPoints:upathBuffer  forRect:&rect  :&lastpoint];

	upathBuffer->ops[upathBuffer->num_ops] = 0;
	upathBuffer->pts[upathBuffer->num_pts] = 0;
	upathBuffer->pts[upathBuffer->num_pts + 1] = 0;

	if (upathBuffer->num_ops > 0)
	{
		PSWSetControlPoints(ControlFont, knobsize, NX_BLACK, 0.15); 
		PSWDrawControlPoints(upathBuffer->pts[0], upathBuffer->pts[1],
			&upathBuffer->pts[2], upathBuffer->num_pts, upathBuffer->ops);
	}

	return self;
}

- drawObject:object  forRect:(NXRect *)r  withFlags:(int) flags
{
	NXRect		rect;

	if (r)
		rect = *r;
	else
		[self  getVisibleRect:&rect];

	[object  drawObject:&rect  withFlags:flags  inView:self];

	return self;
}

/*
*	Fill in the background of the rectangle and then draw the graphics.
*	If NX_DRAWING, then draw into the buffer and composite into
*	this view's window.
*/
- drawSelf:(NXRect *)r :(int) count
{	
	if (NXDrawingStatus == NX_DRAWING)
	{
		[bufferId  lockFocus];
		PSsetgray(NX_WHITE);
		NXRectFill(r);

		[bufferId  unlockFocus];
		compositeBuffer([bufferId gState], r, &r->origin, NX_COPY);
		[bufferId  lockFocus];
	}

	[self  drawObject:graphiclistId  forRect:r  withFlags:REFRESHFLAG];

	if (NXDrawingStatus == NX_DRAWING)
	{
		[bufferId  unlockFocus];
		compositeBuffer([bufferId gState], r, &r->origin, NX_COPY);
		[self  drawControl:selectedlistId  forRect:r  withFlags:NOFLAGS];
	}
	
	return self;			
}

/*
*	This method is only overridden to eliminate during a copy
*	the rectclip and gsave/grestore pairing that results from
*	a lockFocus. These are usually harmless operations but
*	they interfere with trying to produce Illustrator format files.
*/
- display:(NXRect *)r  :(int) count  :(BOOL)flag
{
	if (NXDrawingStatus == NX_COPYING)
	{
		[self  drawSelf:r  :count];
		DPSFlushContext(DPSGetCurrentContext());
	}
	else
		[super  display:r  :count  :flag];

	return self;
}

/*
*	Do not focus if Printing because we don't want the
*	scale factored in.
*/
- (BOOL) lockFocus
{
	if (NXDrawingStatus != NX_PRINTING)
		return [super lockFocus];

	return YES;
}

- unlockFocus
{
	if (NXDrawingStatus != NX_PRINTING)
		return [super unlockFocus];

	return self;
}

- (BOOL)acceptsFirstResponder
{
    return YES;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteTypes(stream, "@", &graphiclistId);

	return self;
}

- read:(NXTypedStream *)stream
{
	[super read:stream];
	NXReadTypes(stream, "@", &graphiclistId);

	return self;
}

- awake
{
	selectedlistId = [GraphicList  new];

	hitPoint = [NXApp  hitPoint];
	upathBuffer = [NXApp upathBuffer];

	bufferId = createBuffer(&DefaultContentRect.size);

	return self;
}

/*
 *	Validates menu commands. It returns NO if the
 *	DrawingView knows that action is not valid now,
 *	otherwise it returns YES.
 *
 *	Using pastecount and pastevalid prevents having to look into
 *	the Pasteboard every time paste: is validated.
 */
- (BOOL)validateCommand:menuCell
{
	SEL			action = [menuCell action];

	id			pasteboardId;

	int			count;

	if (	action == @selector(cut:) ||
		action == @selector(delete:) ||
		action == @selector(copy:) ||
		action == @selector(bringToFront:) ||
		action == @selector(sendToBack:) ||
		action == @selector(originalSize:) ||
		action == @selector(originalRatio:))
	{
		return([selectedlistId count] > 0);
	}
	else if (action == @selector(selectAll:))
	{
		return([graphiclistId count] > 0);
	}
	else if (action == @selector(paste:))
 	{
		pasteboardId = [Pasteboard new];
		count = [pasteboardId changeCount];
		if (count != pastecount)
		{
			pastecount = count;
			pastevalid = (drawPasteType([pasteboardId  types]) != NULL);
		}
		return pastevalid;
	}

	return YES;
}

- (BOOL) knowsPagesFirst:(int *) firstPageNum  last:(int *) lastPageNum
{
	*firstPageNum = 1;
	*lastPageNum = 1;

	return YES;
}

- (BOOL) getRect:(NXRect *) theRect  forPage:(int) page
{
	if (page == 1)
	{
		*theRect = bounds;

		return YES;
	}

	return NO;
}

/*
*	Used when printing. Returns the global resources used in the document.
*/
- addResources:(Resource *) resourceDoc
{
	NXAtom		string;

	if (NXDrawingStatus == NX_COPYING)
	{
		string = NXUniqueString(EpsfProcSet);
		if (!resourceDoc[RES_PROCSETS].states[RES_PRESENT])
			resourceDoc[RES_PROCSETS].states[RES_PRESENT] = [List  new];

		[resourceDoc[RES_PROCSETS].states[RES_PRESENT]
						addObjectIfAbsent:(id) string]; 

		if (!resourceDoc[RES_PROCSETS].states[RES_SUPPLIED])
			resourceDoc[RES_PROCSETS].states[RES_SUPPLIED] = [List  new];

		[resourceDoc[RES_PROCSETS].states[RES_SUPPLIED]
						addObjectIfAbsent:(id) string]; 
	}	

	return self;
}	

/*
*	Print the %%DocumentResource comments. A list of the resources is
*	accumulated from the imported files (only one in this case).
*	The list is then written to the current context.
*/
- beginResourceComments:(const NXRect *) bbox
{
	int				i, j;

	Resource		resourceDoc[RES_NUMTYPES];

	bzero(&resourceDoc, sizeof(resourceDoc));
	[self  addResources:resourceDoc];
	[graphiclistId  addResources:resourceDoc  for:(NXRect *) bbox];

	for (i = 0; i < RES_NUMTYPES; i++)
	{
		for (j = 0; j < RES_NUMSTATES; j++)
		{
			if (resourceDoc[i].states[j])
			{
				WriteEpsfResource(resourceDoc[i].states[j], i, j);
				[resourceDoc[i].states[j]  free];
			}
		}
	}

	return self;
}

/*
*	Write out the necessary information. Overridden to include
*	the fonts from the EPSF files.
*/
- beginPrologueBBox:(const NXRect *)boundingBox 
	creationDate:(const char *)dateCreated 
	createdBy:(const char *)anApplication 
	fonts:(const char *)fontNames 
	forWhom:(const char *)user 
	pages:(int)numPages 
	title:(const char *)aTitle
{
	time_t		clock;

	DPSContext	ctxt;

	ctxt = DPSGetCurrentContext();
	if (!boundingBox)
		boundingBox = [[NXApp  printInfo]  paperRect];

	if (!dateCreated)
	{
		clock = time(0);
		dateCreated = ctime(&clock);
	}

	if (!anApplication)
		anApplication = [NXApp  appName];

	if (!user)
		user = (char *) getlogin();

	if (numPages <= 0)
		numPages = 1;

	if (!aTitle)
		aTitle = [[window  delegate]  filename];

	DPSPrintf(ctxt, "%%!PS-Adobe-2.0 EPSF-1.2\n");
	DPSPrintf(ctxt, "%%%%Creator: %s\n", anApplication);
	DPSPrintf(ctxt, "%%%%For: %s\n", user);
	DPSPrintf(ctxt, "%%%%Title: %s\n", aTitle);
	DPSPrintf(ctxt, "%%%%CreationDate: %s", dateCreated);
	DPSPrintf(ctxt, "%%%%BoundingBox: %d %d %d %d\n", (int) floor(boundingBox->origin.x),
		(int) floor(boundingBox->origin.y),
		(int) ceil(boundingBox->origin.x + boundingBox->size.width),
		(int) ceil(boundingBox->origin.y + boundingBox->size.height));
	if (NXDrawingStatus == NX_COPYING)
		DPSPrintf(ctxt, "%%AI3_TemplateBox: %d %d %d %d\n", 306, 396, 306, 396);
	if (NXDrawingStatus != NX_COPYING)
		DPSPrintf(ctxt, "%%%%Pages: %d\n", numPages);

	[self  beginResourceComments:boundingBox];

	if ([[NXApp  printInfo]  orientation] ==  NX_LANDSCAPE)
		DPSPrintf(ctxt, "%%%%Orientation: Landscape\n");
	else
		DPSPrintf(ctxt, "%%%%Orientation: Portrait\n");

	return self;
}

/*
*	Includes the abbreviated Illustrator proc set so that
*	the imported files produced through Save To will print on their
*	own. Also includes the preview data as a comment when
*	specified.
*/
- endHeaderComments
{
	DPSContext	ctxt;

	if (NXDrawingStatus == NX_COPYING)
	{
		ctxt = DPSGetCurrentContext();
		DPSPrintf(ctxt, "%%%%EndComments\n\n");

		DPSPrintf(ctxt, "%%%%BeginProcSet: EPSF_Illustrator_abbrev 0 0\n");
		WriteEpsfProcSetDef ();
		DPSPrintf(ctxt, "%%%%EndProcSet\n\n");

		if (imageId && [[SaveAsPanel  new]  format] == SAVE_EPSPREVIEW)
			WriteEpsfPreview(imageId);
	}
	else
		[super  endHeaderComments];

	return self;
}

/*
*	If saving in illustrator, override the prologue comment.
*/
- endPrologue
{
	DPSContext	ctxt;

	if (NXDrawingStatus != NX_COPYING)
	{
		ctxt = DPSGetCurrentContext();
		DPSPrintf(ctxt, "%%%%EndProlog\n\n");
	}
	else
		[super  endPrologue];

	return self;
}

/*  Initialize the Illustrator abbreviated proc set. */
- beginSetup
{
	DPSContext	ctxt;

	if (NXDrawingStatus == NX_COPYING)
	{
		ctxt = DPSGetCurrentContext();
		DPSPrintf(ctxt, "%%%%BeginSetup\n");
		WriteEpsfProcSetInit();
	}
	else
		[super  beginSetup];
		
	return self;
}

- endSetup
{
	DPSContext	ctxt;

	if (NXDrawingStatus == NX_COPYING)
	{
		ctxt = DPSGetCurrentContext();
		DPSPrintf(ctxt, "%%%%EndSetup\n");
	}
	else
		[super  endSetup];
		
	return self;
}

/*  Terminate the Illustrator abbreviated proc set. */
- beginTrailer
{
	DPSContext	ctxt;

	if (NXDrawingStatus == NX_COPYING)
	{
		ctxt = DPSGetCurrentContext();
		DPSPrintf(ctxt, "%%%%Trailer\n");
		WriteEpsfProcSetTerm();
	}
	else
		[super  beginTrailer];

	return self;
}

- endTrailer
{
	if (NXDrawingStatus != NX_COPYING)
		[super  endTrailer];

	return self;
}

@end
